Select
Allow users to choose one or more items from a list, where the selected item(s) can represent a value in a form or be used to filter or sort existing content
#Similar components with different purposes
To clarify the differences between Select and ActionMenu, a short description is provided.
| Component name | Usage |
|---|---|
| Select | Choose one or more values from a list. |
| Action Menu | A list of individual actions that the user can perform and links. |
#Examples
#Single Select
A single Select allows the user to choose only one item from a list of mutually exclusive options. If you have more than 7 items, you should use a Select. If you have 3-6 items, you can use either Radios or a Select.
A default Select consists of a button, a down-arrow icon, a list box, and a clear all button that is only displayed when an option is selected.
- Label: when using a standalone
Select, you should always have a label. A label text should inform the user what to select from a list of options. Use Form Element Wrapper around aSelectto get the proper label. - Button: the user can click the button to show a list of items. After selection, the item is displayed in the button.
- Down-arrow icon: indicates if the list box is shown or not.
- List box: contains a list of selectable items that can be grouped into categories and contain an icon, description, or both (see Items Variants below). If you select an item or click outside the list box, it will be hidden.
- Clear all button: the user can remove selected items by clicking the X icon.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);#Multi Select
A multi Select allows the user to select or deselect one or more items.
A multi Select includes all of the elements from a single Select with the following additions:
- Badge: shows the number of selected items.
- Checkmark icon: indicates which item is currently selected in the list.
- Footer: the user can confirm the selection or click the “Cancel” button to reset the selection.
Use array-like values to enable the multi select mode.
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
/>
);#Searchable
The search field allows the user to type a keyword to search for an option. As the user types, the searchable Select filters the results. This helps the user find something quickly in a large list of options. For example, a searchable Select is most often used for filtering a list of countries, as it is extensive, but also easy to answer.
Use a searchable Select when
- the user needs to filter a large list of items.
- the user can quickly find a known option.
For developers
You can use the searchable to control whether or not to display a search bar above the list box. If set to auto (the default), the search bar will be visible only when there are more than 7 items. If set to always, it will be visible no matter the total amount of options. And, if set to never, it will not be displayed.
By default, the search bar will search for the title property of the options. However, you can specify custom searchable content to search for by using the searchableContent prop. This prop should be a function that takes an option as an argument and returns a string that will be used to match the search query. You can see it in action in the Groups example, where the searchable content takes both the option title and description.
For more complex use cases, you can use the onSearch property which is a function that takes a query string as an argument. In this case, the developer is responsible for using this query string to fetch new items and update the options list. Also, the developer must manage when to include the selected options in the list. You can see it in action in the Async examples.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOption}
onChange={setSelectedOption}
searchable="always"
/>
);#Common Use Cases
#With default option
Whenever possible, give the user the optimal option by default. This will help you avoid cases where the alternative options can often lead to errors or be confusing for inexperienced users.
You can specify an option to be selected by default using the defaultOption property. If this property is not provided or is undefined, the first option will be selected by default in single select mode.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of options"
items={[
{ title: "All", value: "all" },
{ title: "Option 1", value: "option-1" },
{ title: "Option 2", value: "option-2" },
]}
value={selectedOption}
onChange={setSelectedOption}
defaultOption="all"
/>
);#Without default option
You can use the noDefaultOption property to not select an option by default. In this case, when the selection is empty, the placeholder will be displayed in the Selectbutton content.
For the Select without default option, a label should be provided to the user. A label should clearly describe the purpose of the selection.
Placeholder text
A placeholder text can serve as a hint, description, or an example of the information required for a particular field.
However, placeholder text should not be used as a label, since it disappears when an item is selected. In most cases, the user will forget what information belongs in a field. As a result, the user with visual and cognitive impairments are faced with an additional burden.
const [selectedOption, setSelectedOption] = useState<number | undefined>();
return (
<Select
aria-label="List of options"
placeholder="Choose one"
items={[
{ title: "Option 1", value: 1 },
{ title: "Option 2", value: 2 },
]}
value={selectedOption}
onChange={setSelectedOption}
noDefaultOption
/>
);With custom keys
In simple terms, the select list box functions as an array of options. As explained in the React documentation, each item in the list requires a key prop, which must be unique. Without this key, the component defaults to using the option's title, which can cause issues if titles aren't unique. If ensuring title uniqueness isn't feasible, it's crucial to assign a unique key to each option, as shown in the example below.
const [selectedOption, setSelectedOption] = useState<number | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{
title: "Apple",
description: "Red color",
value: 1,
key: "red-apple",
},
{
title: "Apple",
description: "Green color",
value: 2,
key: "green-apple",
},
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);#With TypeScript union
type Fruits = "apple" | "banana" | "blueberry" | "cherry";
const [selectedOption, setSelectedOption] = useState<Fruits | undefined>();
return (
<Select
aria-label="List of fruits"
placeholder="Choose a fruit"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOption}
onChange={setSelectedOption}
noDefaultOption
/>
);#Bulk Actions
When many options need to be selected, it is recommended to include "Select all" and "Deselect all" buttons upfront to provide flexibility to the user and speed up the bulk selection process.
The following examples show the number of selected/deselected items:
- Select all (2): indicates that two options in the list were not selected. The user can select the remaining two selectable items at once.
- Deselect all (3): indicates that three options are already selected in the list. The user can deselect all three selected options at once.
Note the "Select all" button is disabled once all items are selected. The "Deselect all" button is only available if the user has at least one item selected. Otherwise, the button is disabled.
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
bulkActions
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
/>
);#Max Number of Items
A badge and hint text indicate the number of items that can be selected in advance. Providing helpful constraints avoids user errors. For example, the user can immediately select the correct number of items instead of receiving an error message after submitting the form.
Note that after selecting the maximum number of items, the other items are disabled. The user must deselect one of the selected items to be able to select another item.
const items = [
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
];
const [selectedOptions, setSelectedOptions] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
items={items}
value={selectedOptions}
onChange={setSelectedOptions}
maxNumberOfItems={2}
/>
);#Creatable
Use creatable Select to allow the user to add a new item to the list for selection. You must also provide the onCreate function that will be called when the user clicks the create button. This function takes the value present on the search field as an argument and must return a new option (type of OptionItem) to be added to the list. The new option will be selected automatically after creating it. You can also change the label of the create button by using thecreateButtonLabel prop.
const [items, setItems] = useState(["Apple", "Banana", "Blueberry", "Cherry"]);
const [selectedOption, setSelectedOption] = useState<string[]>([]);
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
return (
<Select
items={items.map(itemize)}
value={selectedOption}
onChange={setSelectedOption}
creatable
onCreate={(newValue) => {
setItems([...items, newValue].sort((a, b) => a.localeCompare(b)));
return itemize(newValue);
}}
/>
);
#With validation
By default, the list box will be hidden after creating a new option in single select mode and kept open in multi select mode. However, you can control this behavior by returning a boolean in the onCreate function, where false means closing the list box. This is useful when you want to validate the new option before adding it to the list. Here is an example of how to do it:
Pick or create a fruit
const [items, setItems] = useState(["Apple", "Banana", "Blueberry", "Cherry"]);
const [isValid, setIsValid] = useState(true);
const [selectedOption, setSelectedOption] = useState<string[]>([]);
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
const validate = (value: string) => {
return value.length > 0 && value.length < 10;
};
return (
<FormElementWrapper
label="Fruit"
name="Fruit"
invalid={!isValid}
error="Value must be between 1 and 10 characters"
helptext="Pick or create a fruit"
>
<Select
aria-label="List of fruits"
creatable
items={items.map(itemize)}
value={selectedOption}
onChange={(newValue) => {
setSelectedOption(newValue);
setIsValid(true);
}}
onCreate={(value: string) => {
const trimmedValue = value.trim();
const isValidValue = validate(trimmedValue);
if (!isValidValue) {
setIsValid(false);
return false;
}
setIsValid(true);
setItems([...items, trimmedValue].sort((a, b) => a.localeCompare(b)));
return itemize(trimmedValue);
}}
/>
</FormElementWrapper>
);
#Item variants
A selectable item can be provided with an icon and description to give additional context or instructions about what a user should select. Keep items concise and consistent.
- Title only: the text describes the selectable item. Long items where the text is divided into multiple lines are not recommended.
- Title and description: provide additional context for the item, no longer than one line.
- Title, description, and icon: helps the user immediately understand the item. An icon must convey the meaning of the item and should not be used for decorative purposes.
State variations
Each item has the following states:
- Default: the default state is the start or end state, indicating whether the
Selectbutton is empty or filled. - Hover: when a user hovers over an item, a highlight (light gray background) appears indicating that the item is selectable.
- Focus: a blue outline appears around the text area when the user tabs or uses the keyboard to navigate through the items.
- Active/Pressed: a light blue background appears while the item is clicked.
- Selected: the left border and checkmark icon indicate which item in the list is currently selected.
- Disabled: the disabled option should be used infrequently. It indicates that the option is present, but is not currently available to the user.
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple-1" },
{ title: "Apple disabled", value: "apple-2", disabled: true },
{
title: "Apple with description",
description: "Round and usually red or green.",
value: "apple-3",
},
{
title: "Apple with icon",
value: "apple-4",
icon: (
<Icon>
<IconLink />
</Icon>
),
},
{
title: "Apple with icon and description",
description: "Round and usually red or green.",
value: "apple-5",
icon: (
<Icon>
<IconLink />
</Icon>
),
},
{
title: "Apple with icon and description disabled",
value: "apple-6",
description: "Round and usually red or green.",
icon: (
<Icon>
<IconLink />
</Icon>
),
disabled: true,
},
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);#Groups
Group related items in the list to give the user an overview of the list. Empathize with the user. Seeing a list of ungrouped options, can make it hard for the user to find the option they are looking for. Avoid having only one group in a list box.
const [selectedOption, setSelectedOption] = useState<string[]>([]);
return (
<Select
aria-label="List of fruits"
searchable="always"
searchableContent={(item) => item.title + " " + item.description}
items={[
{
heading: "Fruits",
items: [
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
],
},
{
heading: "Vegetables",
items: [
{
title: "Aspargus",
description: "Easily recognizable for its long pointy spears.",
value: "aspargus",
},
{
title: "Carrot",
description: "Orange in color, but purple, black, red, white, and yellow also exist.",
value: "carrot",
},
{
title: "Cocumber",
description: "Usually considered a vegetable, however it's botanically a fruit.",
value: "cocumber",
},
],
},
]}
value={selectedOption}
onChange={setSelectedOption}
/>
);#Async
Use async Select to load asynchronous data from a remote source, either while loading or each time the value changes.
For the examples below, consider the following API:
type Fruit = {
id: number;
name: string;
};
type API = {
fruits: Fruit[];
};
const api: API = {
fruits: [
{ id: 0, name: "Apple" },
{ id: 1, name: "Banana" },
//...
{ id: 14, name: "Watermelon" },
],
};The entire options list can be fetched asynchronously and stored in a state. You can use the loading property to display a spinner in the list box indicating that options are being loaded.
type Fruit = { id: number; name: string };
const [loading, setLoading] = useState<boolean>(false);
const [items, setItems] = useState<OptionItem<Fruit>[]>([]);
const [selectedOption, setSelectedOption] = useState<Fruit | undefined>(api.fruits[1]);
async function getFruits() {
await sleep(5000);
return [...api.fruits];
}
function itemize(data: Fruit[]): OptionItem<Fruit>[] {
return data.map((fruit) => ({
title: fruit.name,
value: fruit,
}));
}
useEffect(() => {
let isSubscribed = true;
setLoading(true);
getFruits()
.then((fruits) => isSubscribed && setItems(itemize(fruits)))
.finally(() => isSubscribed && setLoading(false));
return () => {
isSubscribed = false;
};
}, []);
return (
<Select
loading={loading}
items={items}
value={selectedOption}
onChange={setSelectedOption}
compareFn={(a, b) => a?.id === b?.id}
/>
);
You can also use the onSearch property to fetch new options when the user types in the search bar. You must update the options list with the query results. Be sure to include the selected options in the options list when the search query is empty, otherwise the selection will not be displayed.
type Fruit = { id: number; name: string };
let [items, setItems] = useState<OptionItem<Fruit>[]>([]);
const [selectedOptions, setSelectedOptions] = useState<Fruit[]>([api.fruits[1]]);
const [loading, setLoading] = useState<boolean>(false);
const [query, setQuery] = useState<string>("");
function itemize(data: Fruit[]): OptionItem<typeof selectedOptions>[] {
return data.map((fruit) => ({
title: fruit.name,
value: fruit,
}));
}
function mergeItemsWithSelectedOptions(items: OptionItem<typeof selectedOptions>[]) {
// merge the list of items with selected options, removing duplicates
const selectedItems = itemize(selectedOptions);
return [...items, ...selectedItems].filter(
(item, index, self) => self.findIndex((i) => i.value.id === item.value.id) === index
);
}
function search<T>(items: T[], mapFn: (x: T) => string, query: string, caseSensitive = false) {
const q = caseSensitive ? query : query.toLowerCase();
const map = caseSensitive ? mapFn : (x: T) => mapFn(x).toLowerCase();
return items.filter((item) => map(item).includes(q));
}
async function onSearch(searchQuery: string, caseSensitive: boolean) {
let fetchedItems: OptionItem<typeof selectedOptions>[] = [];
if (searchQuery.length === 0) {
const newItems = itemize(api.fruits.slice(0, 5));
fetchedItems = mergeItemsWithSelectedOptions(newItems);
} else {
await sleep(3000);
const queryedItems = search(api.fruits, (f) => f.name, searchQuery, caseSensitive);
fetchedItems = itemize(queryedItems);
}
setItems(fetchedItems);
setLoading(false);
}
async function onCreate(value: string) {
setLoading(true);
await sleep(3000);
const newFruit = { id: api.fruits.length, name: value };
api.fruits.push(newFruit);
setLoading(false);
return itemize([newFruit])[0];
}
const debouncedSearch = useDebounceFn(onSearch, 500);
useEffect(() => {
debouncedSearch(query, false);
}, [query]);
if (query && loading) {
items = search(items, (i) => i.title, query);
}
return (
<Select
aria-label="List of fruits"
bulkActions
creatable
onChange={setSelectedOptions}
onCreate={onCreate}
onSearch={(q) => {
setLoading(true);
setQuery(q);
}}
loading={loading}
searchHelpText="Start typing to view and select matching items."
items={items}
value={selectedOptions}
/>
);
#Select Overview
Use the showSelectionOverview property to show a list of selected items just below the Select button. The user can easily see all selected items in the list box. In addition, the user can easily deselect items directly from the Selection Overview by clicking the X icon.
- Fruits
- Round and usually red or green.
- Vegetables
- Easily recognizable for its long pointy spears.
const items = [
{
heading: "Fruits",
items: [
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
icon: (
<Icon>
<IconLink />
</Icon>
),
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
],
},
{
heading: "Vegetables",
items: [
{
title: "Aspargus",
description: "Easily recognizable for its long pointy spears.",
value: "aspargus",
},
{
title: "Carrot",
description: "Orange in color, but purple, black, red, white, and yellow also exist.",
value: "carrot",
},
{
title: "Cocumber",
description: "Usually considered a vegetable, however it's botanically a fruit.",
value: "cocumber",
},
],
},
];
const [selectedOptions, setSelectedOptions] = useState<string[]>(["apple", "aspargus"]);
return (
<Select
aria-label="List of fruits and vegetables"
showSelectionOverview
items={items}
value={selectedOptions}
onChange={setSelectedOptions}
/>
);#Invalid
A Select can be marked as having an error to indicate that an entered value is invalid. Use the error message to inform the user what has happened, and then provide guidance on next steps or possible solutions. The best practice is to verify the user's data before they have filled in all the fields of a form.
The error message can also indicate that the input is empty, e.g. "Select a country". This can happen if a user clicks the Submit button before they have fully entered the value. However, if we have correctly marked required or optional fields, the user knows which fields are required (see Best Practices). Follow the writing guideline for Issues and issue descriptions.
Some error message to let the user know what's wrong
const [selectedOption, setSelectedOption] = useState<string | undefined>();
return (
<FormElementWrapper
label="Fruit"
name="Fruit"
invalid
error="Some error message to let the user know what's wrong"
>
<Select
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
onChange={setSelectedOption}
value={selectedOption}
/>
</FormElementWrapper>
);#Disabled
The disabled state indicates that the Select exists but is not available under some circumstances. This can be used to maintain continuity of the layout and to communicate that it may be available later. If possible, provide a hint text or a visual clue to explain why the Select is disabled to avoid user confusion.
const [selectedOption1, setSelectedOption1] = useState<string | undefined>();
const [selectedOption2, setSelectedOption2] = useState<string[]>(["apple", "banana"]);
return (
<>
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
onChange={setSelectedOption1}
value={selectedOption1}
disabled
/>
<br />
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
onChange={setSelectedOption2}
value={selectedOption2}
showSelectionOverview
disabled
/>
</>
);#Custom option element
If the item variants do not meet your requirements, you can use custom option elements. Consistency is critical when using custom option elements. Make sure they have a consistent pattern and contain only the necessary information. This example shows the consistency of labels with basic checkboxes. Note that you can provide custom renderers both for the options in the list box and for the selected items in the selection overview.
- Remove Apple from cart
- Remove Banana from cart
const [selectedOptions, setSelectedOptions] = useState<string[]>(["apple", "banana"]);
return (
<Select
aria-label="List of fruits"
listboxHeading="Shopping list"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry" },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
optionRenderer={(props) => {
const { item, onSelect, isSelected } = props;
return (
<BaseSelectOption key={item.title} style={{ boxShadow: "none" }} {...props}>
<Checkbox
tabIndex={-1}
disabled={item.disabled}
onChange={() => onSelect(item)}
checked={isSelected}
value={item.title}
>
{item.title}
</Checkbox>
</BaseSelectOption>
);
}}
showSelectionOverview
overviewOptionRenderer={(props) => {
const { item } = props;
return (
<BaseOverviewOption key={item.title} {...props}>
<InlineText>
Remove <strong>{item.title}</strong> from cart
</InlineText>
</BaseOverviewOption>
);
}}
/>
);#Custom option title
You can also use a simpler way to create custom option elements. This example shows the use of the useOptionTitleRenderer hook to customize the way the option title is rendered.
const [selectedOptions, setSelectedOptions] = useState<string[]>(["apple"]);
const customRenderers = useOptionTitleRenderer<string[]>((title, opt) => (
<Pill variant={{ type: "static" }} size={opt.isOverview ? "medium" : "small"}>
<InlineText tone={opt.disabled ? "subtle" : "neutralDark"}>
<TextHighlight value={title} needle={opt.needle} caseSensitive={opt.caseSensitive} />
</InlineText>
</Pill>
));
return (
<Select
aria-label="List of fruits"
items={[
{ title: "Apple", value: "apple" },
{ title: "Banana", value: "banana" },
{ title: "Blueberry", value: "blueberry", disabled: true },
{ title: "Cherry", value: "cherry" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
showSelectionOverview
searchable="always"
{...customRenderers}
/>
);#Custom button element
If the default button does not meet your requirements, you can use a custom button element. Consistency is critical when using a custom button element. Make sure it contain only the necessary information. This example shows a label with description.
const [selectedOption, setSelectedOption] = useState<string | undefined>("blueberry");
return (
<Select
aria-label="List of fruits"
buttonRenderer={(option?: OptionItem<string>) =>
option ? (
<BigSmall
style={{ textAlign: "start", maxWidth: "80%" }}
big={option.title}
small={
<span
style={{
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
display: "block",
}}
>
{option.description}
</span>
}
/>
) : (
"Select a fruit"
)
}
buttonProps={{ size: "large" }}
items={[
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
]}
value={selectedOption}
onChange={setSelectedOption}
noDefaultOption
/>
);
#Custom size
Choose an appropriate width
The items should not be wider than the text. The text may be truncated if it's wider than the item, making it hard for users to read.
Custom size support provides more flexibility in structuring layouts and should be used to create a hierarchy of importance within the page. Use a consistent width when used alongside other Form Element Wrapper on the same page. Use the fullWidth size sparingly.
To control the width of the button, you can use
- the
widthproperty to adjust the width of the button. - the
fullWidthproperty to fully extend the width of the button.
By default, the list box has a minimum width of 15 rem, and a maximum width of 37.5 rem. If you want to control the width of the list box, it is recommend to use
- the
listboxWidthproperty to adjust the width of the list box. - the
noListboxMinWidthproperty to disable the default minimum list box width. - the
noListboxMaxWidthproperty to disable the default maximum list box width.
Adjust height for list box
By default, the list box has a maximum height of 20.25 rem, which is about 7.5 items in the list. This means that 7 items will completely fit in the list box and the 8th item will be cut in half, which alongside the scrollbar, visually indicates that there are more items in the list box.
When using custom option elements, the number of items displayed can vary, but make sure the user can clearly see these indicators of list box continuity, especially in a Modal.
Fixed width
Fixed width for button and list box
Full width
Full width with selection overview
- Round and usually red or green.
- It’s a berry and it’s blue.
const [selectedOption, setSelectedOption] = useState<string[]>(["apple", "blueberry"]);
const commomProps = {
items: [
{
title: "Apple",
description: "Round and usually red or green.",
value: "apple",
},
{
title: "Banana",
description: "Oblong thing that starts out green and becomes yellow, then black.",
value: "banana",
},
{
title: "Blueberry",
description: "It’s a berry and it’s blue.",
value: "blueberry",
},
{
title: "Cherry",
description: "Like a blueberry, but larger and red.",
value: "cherry",
},
],
searchable: "always" as const,
bulkActions: true,
value: selectedOption,
onChange: setSelectedOption,
ariaLabel: "List of fruits",
};
return (
<>
<Paragraph>Fixed width</Paragraph>
<Select {...commomProps} buttonWidth={600} />
<br /> <br />
<Paragraph>Fixed width for button and list box</Paragraph>
<Select {...commomProps} buttonWidth={600} listboxWidth={600} />
<br /> <br />
<Paragraph>Full width</Paragraph>
<Select {...commomProps} fullWidth />
<br /> <br />
<Paragraph>Full width with selection overview</Paragraph>
<Select {...commomProps} fullWidth showSelectionOverview />
</>
);#List box placement
Sometimes, the list box may not be visible because it is placed outside the viewport. You can use the placement property to specify where the list box should be placed. The default value is bottom-start.
const [showModal, setShowModal] = useState(false);
const [selectedOptions, setSelectedOptions] = useState<number[]>([]);
return (
<>
<Button onClick={() => setShowModal(true)}>Trigger modal</Button>
<Modal shown={showModal} headerTitle="Placement Example" onClose={() => setShowModal(false)}>
<Modal.Content>
<FormElementWrapper label="Fruit" name="Fruit">
<Select
aria-label="List of fruits"
bulkActions
placement="auto-start"
items={[
{ value: 0, title: "Apple" },
{ value: 1, title: "Banana" },
{ value: 2, title: "Blueberry" },
{ value: 3, title: "Cherry" },
{ value: 4, title: "Grape" },
{ value: 5, title: "Guava" },
{ value: 6, title: "Lemon" },
{ value: 7, title: "Lime" },
{ value: 8, title: "Orange" },
{ value: 9, title: "Peach" },
{ value: 10, title: "Pear" },
{ value: 11, title: "Pineapple" },
{ value: 12, title: "Raspberry" },
{ value: 13, title: "Strawberry" },
{ value: 14, title: "Watermelon" },
]}
value={selectedOptions}
onChange={setSelectedOptions}
/>
</FormElementWrapper>
</Modal.Content>
</Modal>
</>
);#Usage with data-observe-keys
Use data-observe-key on the main component and/or individual items to create identifiers, which are useful for tracking user interactivity, for example. Inner buttons and inputs, such as confirm, cancel, clear, bulk actions, and search bar, will be assigned with the same data-observe-key plus a discriminator. For instance, when using <Select data-observe-key="foo" />, the clear button will be assigned with foo-ClearButton. See below a complete example and note this pattern in action.
const [items, setItems] = useState(["Apple", "Banana", "Blueberry", "Cherry"]);
const [selectedOptions, setSelectedOptions] = useState<string[]>(["banana"]);
const itemize = (value: string): OptionItem<string> => ({
title: value,
value: value.toLowerCase(),
"data-observe-key": `${value}Item`,
});
return (
<Select
bulkActions
showSelectionOverview
data-observe-key="FruitSelect"
aria-label="List of fruits"
items={items.map(itemize)}
value={selectedOptions}
onChange={setSelectedOptions}
creatable
onCreate={(newValue) => {
setItems([...items, newValue]);
return itemize(newValue);
}}
/>
);
#Indeterminate State
The useSelectIndeterminateState hook enhances the Select component by providing a subset of props with a custom state manager. This enables the creation of an intermediate selection state. Let's take a look at an example that demonstrates how to utilize this hook to introduce a new type of selection: partial selection. In this scenario, each option represents a tag, and each tag can be active on zero or multiple websites. A tag is considered fully selected if it is active on all websites. If it is active on some, but not all websites, it is classified as partially selected. Finally, if a tag is not active on any website, it is considered unselected.
- 2 of 6 sites are tagged
- 4 of 6 sites are tagged
- 6 of 6 sites are tagged
type Tag = { name: string; sitesCount: number };
// maximum number of sites
const totalOfSites = 6;
// this state simulates the tags list from your API
const [tagsFromAPI, setTagsFromAPI] = useState<Tag[]>([
{ name: "Tag 1", sitesCount: 0 },
{ name: "Tag 2", sitesCount: 2 },
{ name: "Tag 3", sitesCount: 4 },
{ name: "Tag 4", sitesCount: 6 },
]);
// local copy of the tags list that will be updated with new sites count
const [tags, setTags] = useState<Tag[]>(tagsFromAPI);
const [selectedTags, setSelectedTags] = useState<Tag[]>(tags.filter((tag) => tag.sitesCount > 0));
const onChange = (newSelection: Tag[], updatedTags: Tag[]) => {
// update the local state
setSelectedTags(newSelection);
// submit the changes to your API to update the tag sites count
console.log("Call your API to update all tag sites count", updatedTags);
setTagsFromAPI(updatedTags);
};
const compareFn = (a: Tag, b: Tag) => a.name === b.name;
const itemize = (tag: Tag): OptionItem<Tag> => ({
title: tag.name,
value: tag,
description: `${tag.sitesCount} of ${totalOfSites} sites are tagged`,
});
const indeterminateStateProps = useSelectIndeterminateState<Tag[]>(
tagsFromAPI,
[tags, setTags],
"sitesCount",
totalOfSites,
onChange,
compareFn
);
return (
<Select
{...indeterminateStateProps}
aria-label="List of tags on site"
items={tags.map(itemize)}
value={selectedTags}
showSelectedItemsOnTop={false}
showSelectionOverview
bulkActions
creatable
onCreate={(value: string) => {
const newTag = { name: value, sitesCount: totalOfSites };
setTags([...tags, newTag]);
return itemize(newTag);
}}
/>
);
#Properties
| Property | Description | Defined | Value |
|---|---|---|---|
valueRequired | unknownValue of the form control | ||
onChangeRequired | functionCallback for onChange event | ||
nameOptional | stringName applied to the form control | ||
idOptional | stringId applied to the form control | ||
invalidOptional | booleanIs the form control invalid | ||
onBlurOptional | functionCallback for onBlur event | ||
aria-labelOptional | stringLabel of the form control | ||
aria-describedbyOptional | stringID of an an element that describes what the form control is for | ||
aria-labelledbyOptional | stringID of an an element that labels this form control | ||
itemsOptional | object[]List of options or groups of options | ||
compareFnOptional | functionFunction that return true is two items are equal | ||
placeholderOptional | stringPlaceholder for the select | ||
iconOnlyOptional | booleanPopover anchor becomes an icon-only button | ||
fullWidthOptional | booleanShould the select be full width? | ||
defaultOptionOptional | unknownOption selected by default. If undefined, the default is the first option. Use noDefaultOption property to disable it. | ||
noDefaultOptionOptional | booleanIf true, does not use the default option. | ||
disabledOptional | booleanCan the select button be clicked | ||
loadingOptional | booleanDisplays a spinner in the listbox | ||
searchableOptional | "always" | "auto" | "never"Enables searching functionality. If set to auto, search bar is visible only when there are more than 7 items. | ||
onSearchOptional | functionHandler for searching event | ||
caseSensitiveOptional | booleanEnables case sensitive search. | ||
searchPlaceholderOptional | stringPlaceholder for search field | ||
searchHelpTextOptional | elementPlaceholder for search field | ||
searchLabelOptional | stringAria-label for search field | ||
searchableContentOptional | functionDefines which text content the search should be applied to. Defaults to the options title. | ||
bulkActionsOptional | booleanEnables bulk actions such as select/deselect all | ||
creatableOptional | booleanEnables creating options when the search doesn't exactly match any options | ||
onCreateOptional | functionCallback for onCreate option event | ||
createButtonLabelOptional | stringLabel to be displayed on the create option button | ||
hideClearButtonOptional | booleanHides the button to clear the selection | ||
showSelectedItemsOnTopOptional | booleanIf enabled, shows selected items on top when opening the listbox. In multi-selection mode, it defaults to true. | ||
showSelectionOverviewOptional | booleanShows selected items in a box bellow the select input | ||
overviewLabelOptional | stringLabel that describes the overview list | ||
roleOptional | "listbox" | "menu"Role of the list box | ||
keyboardTypingIntervalOptional | numberAmount of time in milliseconds used to identify that the user has finished typing a string | ||
maxNumberOfItemsOptional | numberMaximum number of items that can be selected | ||
optionRendererOptional | functionCustom renderer for options | ||
overviewOptionRendererOptional | functionCustom renderer for overview options | ||
objectprops to pass down to the button | |||
objectRef of the button | |||
numberControls the width of the select button. | |||
listboxWidthOptional | numberControls the width of the select listbox. | ||
noListboxMinWidthOptional | booleanIf true, the default listbox minimum width won't be set | ||
noListboxMaxWidthOptional | booleanIf true, the default listbox maximum width won't be set | ||
placementOptional | "auto" | "auto-end" | "auto-start" | "bottom" | "bottom-end" | "bottom-start" | "left" | "left-end" | "left-start" | "right" | "right-end" | "right-start" | "top" | "top-end" | "top-start"Preferred placement for the listbox | ||
allowedAutoPlacementsOptional | literal-union[]Allowed placements for the listbox when using an "auto" value for the "placement" prop | ||
hideChevronOptional | booleanHide the chevron icon | ||
functionDefines how the selected item is rendered in the button. Defaults to the option title. | |||
listboxHeadingOptional | elementHeading for the listbox | ||
stateManagerBuilderOptional | functionCustom function to handle selection changes, where the input is a list of options affected and the output is a list of selected options | ||
data-observe-keyOptional | stringUnique string, used by external script e.g. for event tracking | ||
classNameOptional | stringCustom className that's applied to the outermost element (only intended for special cases) | ||
styleOptional | objectStyle object to apply custom inline styles (only intended for special cases) | ||
tabIndexOptional | numberTab index of the outermost HTML element of the component | ||
onKeyDownOptional | functionCallback for onKeyDown event | ||
onMouseDownOptional | functionCallback for onMouseDown event | ||
onMouseEnterOptional | functionCallback for onMouseEnter event | ||
onMouseLeaveOptional | functionCallback for onMouseLeave event | ||
onFocusOptional | functionCallback for onFocus event |
#Guidelines
#Best practices
#Do not use when
#Accessibility
Explore detailed guidelines for this component: Accessibility Specifications
#Writing
#Notable Changes
#Version 52.0.0
The multiple prop and both SingleSelectProps and MultiSelectProps types were removed in favor of a single SelectProps type. With this change, the type inference logic has been improved to determine the mode of the Select based on the value prop's type. If the valueprop is an array, then the multi select mode is enabled, otherwise, the single select mode takes place. These changes streamline the codebase and make it easier to use the Select component.
Therefore, to adapt to these changes, it is necessary to remove the multiple prop and explicit literals in the React code and use the new common SelectProps type.
#Before
const [value, setValue] = useState<string[]>([]);
<Select<string>
multiple
value={value}
onChange={setValue}
/>
#After
const [value, setValue] = useState<string[]>([]);
<Select
value={value}
onChange={setValue}
/>
#Version 60.0.0
The onCreate function must now return an OptionItem or a boolean. Also, developers don't need to include the newly created option in the list of selected options, as the component will automatically select it.
#Before
const [items, setItems] = useState<string[]>(["Foo", "Bar"]);
const [selectedOption, setSelectedOption] = useState<string>();
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
<Select
items={items.map(itemize)}
value={selectedOption}
onChange={setSelectedOption}
creatable
onCreate={(newValue) => {
setItems([...items, newValue]);
setSelectedOption(newValue);
}}
/>
#After
const [items, setItems] = useState<string[]>(["Foo", "Bar"]);
const [selectedOption, setSelectedOption] = useState<string>();
const itemize = (value: string): OptionItem<string> => ({ title: value, value });
<Select
items={items.map(itemize)}
value={selectedOption}
onChange={setSelectedOption}
creatable
onCreate={(newValue) => {
setItems([...items, newValue]);
return itemize(newValue);
}}
/>